package com.ar4j.util;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;

/**
 * A wrapper for EHCache's Cache instance that allows access semantics similar to those 
 * of a Map. The default cache manager is used if none is provided.
 *
 * Some of this is taken from the CacheWrapper recipe page on EHcache's site:
 * http://ehcache.org/recipes/wrapper.html
 */
public class ObjectCache<K, V> {
  private final String name;
  private final Cache cache;
  
  /**
   * Construct a wrapper around a cache with the given name, to be added to the given
   * cache manager, and configured using the given configuration object.
   */
  public ObjectCache(String name, CacheManager manager, CacheConfiguration configuration) {
    Cache newCache = new Cache(configuration);
    manager.addCache(newCache);
    
    this.name = name;
    this.cache = newCache;
  }
  
  /**
   * Construct a wrapper around a cache with the given name, to be added to the given
   * cache manager, and configured to be an LRU cache with the given number of maximum 
   * objects and the given object timeout.
   * 
   * NOTE: the timeout is in seconds
   */
  public ObjectCache(String name, CacheManager manager, int maxObjects, long timeout) {
    this(name, 
        manager, 
        new CacheConfiguration(name, maxObjects)
          .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU)
          .overflowToDisk(false)
          .timeToLiveSeconds(timeout)
          .timeToIdleSeconds(timeout)
    );
  }
  
  /**
   * Construct a wrapper around a cache with the given name, to be added to the default
   * cache manager, and configured to be an LRU cache with the given number of maximum 
   * objects and the given object timeout.
   * 
   * NOTE: the timeout is in seconds
   */
  public ObjectCache(String name, int maxObjects, long timeout) {
    this(name, CacheManager.getInstance(), maxObjects, timeout);
  }
  
  /**
   * @return the name of this cache within the CacheManager used in construction
   */
  public String getName() {
    return name;
  }
  
  /**
   * @return the Cache instance that this object is wrapping
   */
  public Cache getUnderlyingCache() {
    return cache;
  }
  
  /**
   * Put a value into the cache mapped by its key
   */
  public void put(K key, V value) {
    cache.put(new Element(key, value));
  }
  
  /**
   * @return true if the cache contains a value for the given key that is not
   * expired, and false otherwise
   */
  public boolean contains(K key) {
    Element candidate = cache.get(key);
    return (candidate != null && !candidate.isExpired());
  }
  
  /**
   * Retrieves a value from the cache if it is present and is not expired and returns null
   * if one is not found
   */
  @SuppressWarnings("unchecked")
  public V get(K key) {
    Element candidate = cache.get(key);
    if(candidate != null && !candidate.isExpired()) {
      return (V) candidate.getObjectValue();
    }
    
    return null;
  }

}
